package org.budo.graph.service.impl;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.graph.Constant;
import org.budo.graph.druid.filter.stat.BudoStatFilter;
import org.budo.graph.entity.AppGraph;
import org.budo.graph.entity.CacheGraph;
import org.budo.graph.entity.EhcacheStatistics;
import org.budo.graph.entity.SpringGraph;
import org.budo.graph.entity.SqlGraph;
import org.budo.graph.entity.UrlGraph;
import org.budo.graph.repository.AbstractBudoGraphRepository;
import org.budo.graph.repository.BudoGraphRepository;
import org.budo.graph.repository.redis.JedisBudoGraphRepository;
import org.budo.graph.service.BudoGraphService;
import org.budo.graph.service.util.BudoGraphServiceUtil;
import org.budo.graph.service.util.BudoGraphUtil;
import org.budo.graph.service.util.EhcacheStatisticsUtil;
import org.budo.graph.view.util.DruidDataSourceUtil;
import org.budo.graph.view.util.EhcacheGraphUtil;
import org.budo.support.java.net.util.JavaNetUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.time.Time;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.alibaba.druid.stat.DruidStatManagerFacade;
import com.alibaba.druid.support.http.stat.WebAppStatManager;
import com.alibaba.druid.support.spring.stat.SpringStatManager;
import com.alibaba.dubbo.config.ApplicationConfig;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.CacheManager;
import redis.clients.jedis.JedisPool;

/**
 * @author lmw
 */
@Slf4j
@Getter
@Setter
public abstract class AbstractBudoGraphService implements BudoGraphService, ApplicationContextAware, InitializingBean, Constant.Repository {
    /**
     * 类是否存在
     */
    private static final Boolean HAS_BUDO_CACHE = ReflectUtil.hasClass("org.budo.ehcache.config.annotation.EhCacheConfig");

    private final Map<String, Object> LAST_VALUE_MAP = new ConcurrentHashMap<String, Object>();

    private static final Map<String, Object> PROPERTIES_DEFAULT = MapUtil.stringObjectMap( //
            "recordDataPeriod", 60 * 1000, //
            "recordDataDelay", 60 * 1000 //
    );

    private ApplicationContext applicationContext;

    private JedisPool jedisPool;

    private Map<String, Object> properties = PROPERTIES_DEFAULT;

    private BudoGraphRepository budoGraphRepository;

    private String applicationName;

    public AbstractBudoGraphRepository getBudoGraphRepository() {
        if (null != this.budoGraphRepository) {
            return (AbstractBudoGraphRepository) this.budoGraphRepository;
        }

        synchronized (AbstractBudoGraphService.class) {
            if (null != this.budoGraphRepository) {
                return (AbstractBudoGraphRepository) this.budoGraphRepository;
            }

            JedisBudoGraphRepository jedisBudoGraphRepository = new JedisBudoGraphRepository();
            jedisBudoGraphRepository.setJedisPool(AbstractBudoGraphService.this.getJedisPool());
            return (AbstractBudoGraphRepository) (this.budoGraphRepository = jedisBudoGraphRepository);
        }
    }

    public Map<String, Object> getProperties() {
        return this.properties;
    }

    /**
     * 覆盖，不替换
     */
    public void setProperties(Map<String, Object> properties) {
        this.properties.putAll(properties);
    }

    protected int getRecordDataPeriod() {
        Object recordDataPeriod = this.getProperties().get("recordDataPeriod");
        if (null != recordDataPeriod) {
            return Integer.valueOf(recordDataPeriod.toString());
        }

        return 60 * 1000;
    }

    /**
     * 启动延迟，单位毫秒
     */
    protected int getRecordDataDelay() {
        Object recordDataDelay = this.getProperties().get("recordDataDelay");
        if (null != recordDataDelay) {
            return Integer.valueOf(recordDataDelay.toString());
        }

        return 60 * 1000;
    }

    public String getApplicationName() {
        if (null != this.applicationName) {
            return this.applicationName;
        }

        Object applicationName = this.getProperties().get("applicationName");
        if (null != applicationName) {
            return applicationName.toString();
        }

        // 从 dubbo:application name 中取
        ApplicationConfig applicationConfig = this.getApplicationContext().getBean(ApplicationConfig.class);
        String name = applicationConfig.getName();
        if (null == name) {
            return "dubbo-application-name-is-null";
        }

        this.applicationName = name;
        return name;
    }

    public void writeData(String applicationName) {
        long start = System.currentTimeMillis();

        try {
            // writeUrlData
            this.writeUrlData(applicationName);

            // writeAppData
            this.writeAppData(applicationName);

            // writeCacheData
            this.writeCacheData(applicationName);

            // writeSpringData
            this.writeSpringData(applicationName);

            // writeSqlData
            this.writeSqlData(applicationName);
        } catch (OutOfMemoryError e) { // 可能会发生内存溢出 Java heap space
            log.error("#62 writeData OutOfMemoryError, e=" + e, e);
        } catch (Throwable e) {
            log.error("#64 writeData error, e=" + e, e);
        }

        log.info("#62 writeData, this=" + this + ", 耗时 " + (System.currentTimeMillis() - start) + "ms, applicationName=" + applicationName);
    }

    private void writeSqlData(String applicationName) {
        DruidStatManagerFacade druidStatManager = DruidStatManagerFacade.getInstance();
        List<Map<String, Object>> sqlStatDataList = druidStatManager.getSqlStatDataList(null);

        if (null == sqlStatDataList || sqlStatDataList.isEmpty()) {
            return; // 没有监控数据，返回
        }

        for (Map<String, Object> sqlStat : sqlStatDataList) {
            String sql = sqlStat.get("SQL") + "";
            if (!BudoStatFilter.hasBudoGraph(sql)) { // 未加注解，跳过
                continue;
            }

            // 开始正式处理
            SqlGraph sqlGraph = new SqlGraph(sqlStat);

            // 将IP地址作为Key的一部分，假定同一个IP上不会提供同一个接口的多个实现，否则写redis时可能会错乱冲突
            String sql_2 = BudoGraphServiceUtil.SQL_2(sql);
            String key = BUDO_GRAPH + LINKER + SqlGraph.GRAPH_TYPE + LINKER + JavaNetUtil.getHostInnerFirst_1() + LINKER + applicationName + LINKER + sql_2;
            SqlGraph lastSqlGraph = (SqlGraph) LAST_VALUE_MAP.get(key);

            if (null == lastSqlGraph || sqlGraph.getExecuteCount() <= lastSqlGraph.getExecuteCount()) {
                LAST_VALUE_MAP.put(key, sqlGraph);
                continue;
            }

            Map<String, String> minusMap = sqlGraph.minus(lastSqlGraph);
            budoGraphRepository.writeData(SqlGraph.GRAPH_TYPE, applicationName, sql_2, minusMap, Time.now().toTimestamp());

            // 记录上一次的值，用于下次计算差值
            LAST_VALUE_MAP.put(key, sqlGraph);
        }
    }

    /**
     * @see #writeUrlData(String, JedisPool)
     * @see #readSpringLine(JedisPool, String, Map)
     */
    private void writeSpringData(String applicationName) {
        Set<String> springGraphMethods = BudoGraphUtil.getSpringGraphMethods();
        if (null == springGraphMethods || springGraphMethods.isEmpty()) {
            return; // 未配置，直接返回
        }

        // countMax
        Integer countMax = springGraphMethods.size();
        Integer countDone = 0;

        SpringStatManager springStatManager = SpringStatManager.getInstance();
        List<Map<String, Object>> methodStatData = springStatManager.getMethodStatData();

        if (null == methodStatData || methodStatData.isEmpty()) {
            return; // 没有监控数据，返回
        }

        for (Map<String, Object> methodStat : methodStatData) {
            if (countDone > countMax) { // 已经处理完成了所有配置过的Url，返回
                break;
            }

            String method = methodStat.get("Method") + "";
            if (!springGraphMethods.contains(method)) { // 此方法未加注解，跳过
                continue;
            }

            // 已经处理的Url的计数++
            countDone++;

            // 开始正式处理
            SpringGraph springGraph = new SpringGraph(methodStat);

            // 将IP地址作为Key的一部分，假定同一个IP上不会提供同一个接口的多个实现，否则写redis时可能会错乱冲突
            String key = BUDO_GRAPH + LINKER + SpringGraph.GRAPH_TYPE + LINKER + JavaNetUtil.getHostInnerFirst_1() + LINKER + applicationName + LINKER + method;
            SpringGraph lastSpringGraph = (SpringGraph) LAST_VALUE_MAP.get(key);

            if (null == lastSpringGraph || springGraph.getExecuteCount() <= lastSpringGraph.getExecuteCount()) {
                LAST_VALUE_MAP.put(key, springGraph);
                continue;
            }

            Map<String, String> minusMap = springGraph.minus(lastSpringGraph);
            budoGraphRepository.writeData(SpringGraph.GRAPH_TYPE, applicationName, method, minusMap, Time.now().toTimestamp());

            // 记录上一次的值，用于下次计算差值
            LAST_VALUE_MAP.put(key, springGraph);
        }
    }

    /**
     * 写缓存监控数据
     */
    private void writeCacheData(String applicationName) {
        if (!HAS_BUDO_CACHE) {
            return;
        }

        CacheManager cacheManager = EhcacheGraphUtil.getCacheManager();
        if (null == cacheManager) {
            return;
        }

        // 正式开始写数据
        List<EhcacheStatistics> ehcacheStatisticsList = EhcacheStatisticsUtil.listEhcacheStatistics(cacheManager);
        for (EhcacheStatistics ehcacheStatistics : ehcacheStatisticsList) {
            String cacheName = ehcacheStatistics.getCacheName();

            if (!EhcacheGraphUtil.isCacheGraphOpen(cacheName)) {
                continue;
            }

            CacheGraph cacheGraph = new CacheGraph(ehcacheStatistics);

            // 将IP地址作为Key的一部分，假定同一个IP上不会提供同一个接口的多个实现，否则写redis时可能会错乱冲突
            String key = BUDO_GRAPH + LINKER + CacheGraph.GRAPH_TYPE + LINKER + JavaNetUtil.getHostInnerFirst_1() + LINKER + applicationName + LINKER + cacheName;
            CacheGraph lastCacheGraph = (CacheGraph) LAST_VALUE_MAP.get(key);

            if (null == lastCacheGraph || cacheGraph.getLocalHeapHitCount() <= lastCacheGraph.getLocalHeapHitCount()) {
                LAST_VALUE_MAP.put(key, cacheGraph);
                continue;
            }

            Map<String, String> minusMap = cacheGraph.minus(lastCacheGraph);
            budoGraphRepository.writeData(CacheGraph.GRAPH_TYPE, applicationName, cacheName, minusMap, Time.now().toTimestamp());

            // 记录上一次的值，用于下次计算差值
            LAST_VALUE_MAP.put(key, cacheGraph);
        }
    }

    private void writeAppData(String applicationName) {
        WebAppStatManager webAppStatManager = WebAppStatManager.getInstance();
        List<Map<String, Object>> webAppStatData = webAppStatManager.getWebAppStatData();

        for (Map<String, Object> webAppStat : webAppStatData) {
            AppGraph appGraph = new AppGraph(webAppStat, DruidDataSourceUtil.getDruidDataSourceList());

            // 将IP地址作为Key的一部分，假定同一个IP上不会提供同一个接口的多个实现，否则写redis时可能会错乱冲突
            String key = BUDO_GRAPH + LINKER + AppGraph.GRAPH_TYPE + LINKER + JavaNetUtil.getHostInnerFirst_1() + LINKER + applicationName + LINKER + "0";
            AppGraph lastAppGraph = (AppGraph) LAST_VALUE_MAP.get(key);

            if (null == lastAppGraph || appGraph.getRequestCount() <= lastAppGraph.getRequestCount()) { // 第一条记录或数据未增加
                LAST_VALUE_MAP.put(key, appGraph);
                return; // TODO 可能有多个？
            }

            Map<String, String> minusMap = appGraph.minus(lastAppGraph);
            budoGraphRepository.writeData(AppGraph.GRAPH_TYPE, applicationName, "0", minusMap, Time.now().toTimestamp());

            // 记录上一次的值，用于下次计算差值
            LAST_VALUE_MAP.put(key, appGraph);
        }
    }

    private void writeUrlData(String applicationName) {
        Set<String> budoGraphMethods = BudoGraphUtil.getBudoGraphMethods();
        if (null == budoGraphMethods || budoGraphMethods.isEmpty()) {
            return; // 未配置，直接返回
        }

        // countMax
        Integer countMax = budoGraphMethods.size();
        Integer countDone = 0;

        WebAppStatManager webAppStatManager = WebAppStatManager.getInstance();
        List<Map<String, Object>> uriStatData = webAppStatManager.getURIStatData();

        if (null == uriStatData || uriStatData.isEmpty()) {
            return; // 没有监控数据，返回
        }

        for (Map<String, Object> uriStat : uriStatData) {
            if (countDone > countMax) { // 已经处理完成了所有配置过的Url，返回
                break;
            }

            String uri = uriStat.get(STAT_URI) + "";
            if (!budoGraphMethods.contains(uri)) { // 此方法未加注解，跳过
                continue;
            }

            // 已经处理的Url的计数++
            countDone++;

            // 开始正式处理
            UrlGraph urlGraph = new UrlGraph(uriStat);

            // 将IP地址作为Key的一部分，假定同一个IP上不会提供同一个接口的多个实现，否则写redis时可能会错乱冲突
            String key = BUDO_GRAPH + LINKER + UrlGraph.GRAPH_TYPE + LINKER + JavaNetUtil.getHostInnerFirst_1() + LINKER + applicationName + LINKER + uri;
            UrlGraph lastUrlGraph = (UrlGraph) LAST_VALUE_MAP.get(key);

            if (null == lastUrlGraph || urlGraph.getRequestCount() <= lastUrlGraph.getRequestCount()) {
                LAST_VALUE_MAP.put(key, urlGraph);
                continue;
            }

            Map<String, String> minusMap = urlGraph.minus(lastUrlGraph);
            budoGraphRepository.writeData(UrlGraph.GRAPH_TYPE, applicationName, uri, minusMap, Time.now().toTimestamp());

            // 记录上一次的值，用于下次计算差值
            LAST_VALUE_MAP.put(key, urlGraph);
        }
    }
}